package net.blogways.ordermgr.entity;

import com.asiainfo.bpc.service.ChainService;
import com.asiainfo.bpc.utils.ToolFactory;

import net.blogways.ordermgr.util.DBTools;
import net.blogways.ordermgr.util.SeqGenerator;
import org.apache.commons.chain.Context;
import org.apache.commons.chain.impl.ContextBase;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.CollectionUtils;

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.regex.Pattern;

/**
 * @author wanghui
 * Created on 2017/9/14.
 */
public class KeyGenerator {

  private Pattern unique = Pattern.compile("^#.*#$");

  private Map<String, GeneratedKeys> ruleMapper = new LinkedHashMap<>();

  public KeyGenerator(String rules) {
    String[] rulesArr = rules.split(",");
    for (String rule : rulesArr) {
      pushMapper(rule);
    }
  }

  private void pushMapper(String rule) {
    String[] ruleArr = rule.split(":");
    String stub = ruleArr[0];
    String genTag = null;
    String mode = null;
    String ruleDetail = null;
    switch (ruleArr.length) {
      case 2: {
        ruleDetail = ruleArr[1];
        break;
      }
      case 3: {
        if (ruleArr[1].startsWith("?")) {
          genTag = ruleArr[1];
        } else {
          mode = ruleArr[1];
        }
        ruleDetail = ruleArr[2];
        break;
      }
      case 4: {
        if (ruleArr[1].startsWith("?")) {
          genTag = ruleArr[1];
          mode = ruleArr[2];
          ruleDetail = ruleArr[3];
        }
        break;
      }
    }
    String stubKey = unique.matcher(stub).matches() ?
            stub.substring(1, stub.length() - 1) :
            stub;
    this.ruleMapper.put(stubKey, new GeneratedKeys(stub, genTag, mode, ruleDetail));
  }

  public Map<String, Object> getKeys(Map tuple) throws Exception {

    Map<String, Object> keys = new LinkedHashMap<>();

    for (String stub : ruleMapper.keySet()) {
      GeneratedKeys generatedKeys = ruleMapper.get(stub);
      Object key = generatedKeys.getKey();
      if (key == null) {
        key = generatedKeys.generate(tuple);
      }
      if (key != null) {
        keys.put(stub, key);
      }
      tuple.put(stub, key);
      generatedKeys.riseGenerateCount();
    }

    return keys;
  }

  public void resetCounter() {
    for (String stub : ruleMapper.keySet()) {
      ruleMapper.get(stub).resetCounter();
    }
  }

  private class GeneratedKeys {

    private String stub;

    private String genTag;

    private String mode;

    private String ruleDetail;

    private Map<String, Object> keys = new HashMap<>();

    private int generateCount = 0;

    private int resetCount = 0;

    GeneratedKeys(String stub, String genTag, String mode, String ruleDetail) {
      this.stub = stub;
      this.genTag = genTag;
      this.mode = mode;
      this.ruleDetail = ruleDetail;
    }

    Object generate(Map tuple) throws Exception {

      String formatStub = formatStub();
      Object key;

      if (this.genTag != null && !tuple.containsKey(this.genTag.substring(1))) {
        return null;
      }

      if (this.mode != null) {

        if (this.mode.startsWith("%")) {

          key = generateModId(tuple, this.mode, this.ruleDetail);
          this.keys.put(formatStub, key);

        } else {
          key = null;
        }
      } else {

        if ("NOW".equals(this.ruleDetail)) {

          key = DBTools.queryCurrentTime();
          this.keys.put(formatStub, key);

        } else if (StringUtils.isNumeric(this.ruleDetail)) {

          key = generateNumericId(this.stub, this.ruleDetail);
          this.keys.put(formatStub, key);

        } else {
          key = null;
        }
      }
      return key;
    }

    void resetCounter() {
      this.generateCount = 0;
      this.resetCount++;
    }

    private void riseGenerateCount() {
      this.generateCount++;
    }

    Object getKey() throws ExecutionException {
      String formatStub = formatStub();
      return this.keys.get(formatStub);
    }

    String formatStub() {
      if (unique.matcher(this.stub).matches()) {
        return this.stub.substring(1, this.stub.length() - 1);
      }
      if (this.mode != null && this.mode.startsWith("%")) {
        return this.stub + "-" + this.resetCount + "-" + this.generateCount;
      }
      if ("NOW".equals(this.ruleDetail)) {
        return this.stub;
      }
      if (StringUtils.isNumeric(this.ruleDetail)) {
        return this.stub + "-" + this.generateCount;
      }
      return this.stub + "-" + this.resetCount + "-" + this.generateCount;
    }

    /**
     * 主键生成主方法
     *
     * @param stub       主键名
     * @param ruleDetail 主键位数
     * @return
     * @throws Exception
     */
    Object generateNumericId(String stub, String ruleDetail) throws Exception {
      if("18".equals(ruleDetail)){
        return SeqGenerator.getInstance().next(stub);
      }
      ChainService service = ToolFactory.getInstance().getChainService();
      Context paramContext = new ContextBase();
      paramContext.put("stub", stub);
      paramContext.put("ruleDetail", ruleDetail);
      Context result = service.serviceEntry("tcs.common:getTicketFlow", paramContext);
      if (!CollectionUtils.isEmpty(result) && result.containsKey("seqId")) {
        return result.get("seqId");
      }
      throw new RuntimeException("生成序列错误!");
    }


    Object generateModId(Map tuple, String mode, String ruleDetail) {

      String key = mode.substring(1);
      Object value = "";
      if (tuple.containsKey(key)) {
        value = tuple.get(key);
      } else if (tuple.containsKey("GEN_" + key)) {
        value = tuple.get("GEN_" + key);
      } else if (tuple.containsKey("V" + key)) {
        value = tuple.get("V" + key);
      }
      int pow = Integer.parseInt(ruleDetail);
      return Math.floorMod(Long.parseLong(value.toString()), (long) Math.pow(10, pow));
    }
  }
}
